using GLib;
using DBus;
using Xml;

namespace Malem {

	private errordomain InterfaceParsingError {
		UNREACHEABLE,
		EMPTY_DOCUMENT,
		SYNTAX
	}

	public class Mathusalem:GLib.Object, MathusalemIface {
		
		private Connection conn;
		private JobsManager jobsManager;
		private Gee.HashMap<string, ListenerObject> listenerObjects;
		private dynamic DBus.Object freedesktopobject;


		public Mathusalem() {}

		private void supported_interfaces_parser (string dbus_name, string dbus_path, Gee.ArrayList<string> interface_names) throws InterfaceParsingError {
			dynamic DBus.Object proxy;
			string introspect_xml;

			proxy = this.conn.get_object(dbus_name, dbus_path, "org.freedesktop.DBus.Introspectable");
			try {
				introspect_xml = proxy.Introspect();
			} catch (GLib.Error e) {
			        throw new InterfaceParsingError.UNREACHEABLE ("unable to call the introspect method for %s:%s", dbus_name, dbus_path);
			}
	
			Xml.Doc* xml_doc;
			xml_doc = Parser.parse_memory(introspect_xml, (int)introspect_xml.size());
		  if (xml_doc == null) {
		      throw new InterfaceParsingError.SYNTAX ("unable to build the xml document for %s:%s", dbus_name, dbus_path);
		  }

		  //get the root node. notice the dereferencing operator -> instead of .
		  Xml.Node* root_node = xml_doc->get_root_element ();
		  if (root_node == null) {
		      //free the document manually before throwing because the garbage collector can't work on pointers
		      delete xml_doc;
		      throw new InterfaceParsingError.EMPTY_DOCUMENT ("the xml document is empty for %s:%s", dbus_name, dbus_path);
		  }

		  //let's parse those nodes
			Xml.Node *node = null;
		  string node_name="";
		  for (Xml.Node* iter = root_node->children; iter != null; iter = iter->next) {
		      //spaces btw. tags are also nodes, discard them
		      if (iter->type != ElementType.ELEMENT_NODE) continue;

				for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {
					if (prop->name=="name") {
						node_name = prop->children->content;
						break;
					}
				}
				if (node_name == "") continue;
				if (node_name.has_prefix("org.freedesktop.DBus")) continue;
				node = iter;
				interface_names.add(node_name.down());
			}
			delete xml_doc;
		}
	
		public bool exposeJob(string dbus_name, string dbus_path) {
			Gee.ArrayList<string> interface_list = new Gee.ArrayList<string>(str_equal);
			try {
				supported_interfaces_parser(dbus_name, dbus_path, interface_list);
			} catch (GLib.Error e) {
				return false;
			}
			Malem.Util.sortArrayListByString(interface_list, 0, interface_list.size-1);

			dynamic DBus.Object dbusjob = this.conn.get_object(dbus_name, dbus_path, "org.gnome.Mathusalem.TimeConsumingJob");
			dbusjob.onCompleted += dbus_callback_on_completed_job;

			JobObject job = new JobObject(dbus_name, dbus_path, interface_list);
			job.setDBusObject(dbusjob);

			return this.jobsManager.addJob(job);
		}
	
		private string createListener(string interfaces_name_1) {
			string[] inames = interfaces_name_1.split(",");
			string interfaces_name = "";
			foreach(string s in inames)
				if (interfaces_name.length==0) interfaces_name = s.strip().down();
				else interfaces_name = interfaces_name + ","+s.strip().down();

			string dbus_path = "/org/gnome/Mathusalem/interfaces/"+(interfaces_name.hash()).to_string();
			ListenerObject ld = listenerObjects.get(interfaces_name);
			if (ld==null) {
				ld = new ListenerObject(interfaces_name, this.jobsManager);
				conn.register_object(dbus_path, ld);
				listenerObjects.set(interfaces_name, ld);
			}
			return dbus_path;
		}
	
		construct {
			jobsManager = new Malem.JobsManager();
			this.jobsManager.onCompleted += this.on_completed_jobs;
			this.jobsManager.onStarted += this.on_started_jobs;
			listenerObjects = new Gee.HashMap<string, ListenerObject>(str_hash, str_equal);
			uint ret;

			try {
				this.conn = Bus.get(BusType.SESSION);
			} catch (DBus.Error e) {
				error("unable to connect to the DBus Service");
			}

			this.freedesktopobject = this.conn.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus");
			try {
				ret = this.freedesktopobject.request_name("org.gnome.Mathusalem", (uint)0);
			} catch (GLib.Error e) {
				error("unable to register the name \"org.gnome.Mathusalem\"");
			}
			if (ret == RequestNameReply.PRIMARY_OWNER) {
				conn.register_object("/org/gnome/Mathusalem", this);
			} else {
				error("unable to gain the owner of \"org.gnome.Mathusalem\", Mathualem maybe already running.");
			}
		
			this.freedesktopobject.NameOwnerChanged += on_name_owner_changed;
		}

		private void on_lost_dbus_name(string dbus_name) {
			Gee.ArrayList<JobObject> gaj;
			gaj = this.jobsManager.getJobsByDBusName(dbus_name);
			if (gaj!=null) {
				JobObject[] jobs;
				int i = 0;
				jobs = new JobObject[gaj.size];
				foreach(JobObject job in gaj)
					jobs[i++] = job;
				gaj = null;
				foreach(JobObject job in jobs)
					this.jobsManager.removeJob(job, false);
			}
		}

		public string[]	getCurrentJobs() {
			Gee.Collection<JobObject> col = this.jobsManager.getJobs();
			if ((col==null) || (col.size==0)) return new string[0];
//			if ((col==null) || (col.size==0)) return new ObjectAddress[0];

			string[] ret;
			int i=0;
			ret = new string[col.size];
			Gee.ArrayList<JobObject> j = new Gee.ArrayList<JobObject>();
			foreach(JobObject job in col) {
				ret[i] = job.dbusPath+":"+job.dbusName;
//				ret[i].dbus_name = job.dbusName;
				i++;
			}
			return ret;
		}

	/*
		handler of: org.freedesktop.DBus.NameOwnerChanged
	*/
		private void on_name_owner_changed(dynamic DBus.Object sender, string dbus_name, string old_owner, string new_owner) {
			if (new_owner=="")
				this.on_lost_dbus_name(dbus_name);
		}

		private void dbus_callback_on_completed_job(dynamic DBus.Object sender) {
			Malem.JobObject job = this.jobsManager.getJob(sender.get_bus_name(), sender.get_path());
			this.jobsManager.removeJob(job, true);
		}

		private void on_completed_jobs(JobsManager jobs, JobObject job, bool isCompletedWithSuccess) {
			this.onCompletedJob(job.dbusName, job.dbusPath, isCompletedWithSuccess);
		}

		private void on_started_jobs(JobsManager jobs, JobObject job) {
			message("new job: %s:%s\n", job.dbusName, job.dbusPath);
			this.onStartedJob(job.dbusName, job.dbusPath);
		}


		public void getVersion(out int major, out int minor, out int micro) {
			major = 0;
			minor = 0;
			micro = 6;
		}

		public static int main (string[] args) {
			MainLoop loop = new MainLoop(null, false);
			Malem.Mathusalem sm = new Malem.Mathusalem();

			loop.run();
		  return 0;
		}

	}

}
